package org.urbansoft.icl.writer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;

import org.apache.log4j.Logger;
import org.urbansoft.icl.CashLetter;
import org.urbansoft.icl.ForwardPresentmentBundle;
import org.urbansoft.icl.ICLFile;
import org.urbansoft.icl.ICLItem;
import org.urbansoft.icl.ImageView;
import org.urbansoft.icl.reader.ICLReader;
import org.urbansoft.icl.util.FileEncoding;
import org.urbansoft.icl.util.RecordLength;
import org.urbansoft.iso.ISOUtil;
import org.urbansoft.icl.Item;
import org.urbansoft.icl.ReturnItem;

/**
 * The Class ICLFileWriter.
 */
public class ICLFileWriter {
	private ICLFile iclFile;
	private File fileOut;
	private FileOutputStream oStream;
	private byte[] len;
	private byte[] data;
	private String texto;

	private static Logger log = Logger.getLogger(ICLFileWriter.class);

	/**
	 * Instantiates a new iCL file writer.
	 */
	public ICLFileWriter() {
	}

	/**
	 * Instantiates a new iCL file writer.
	 * 
	 * @param iclFile
	 *            the icl file
	 * @param fileOut
	 *            the file out
	 */
	public ICLFileWriter(ICLFile iclFile, File fileOut) {
		this.iclFile = iclFile;
		this.fileOut = fileOut;
	}

	/**
	 * Write file.
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public void writeFile() throws IOException {
		oStream = new FileOutputStream(fileOut);

		if (iclFile == null || fileOut == null) {
			return;
		}

		/* write the file section by section */
		writeFileData();

		oStream.close();
	}

	/**
	 * Write file data.
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private void writeFileData() throws IOException {
		/* write the file header */
		len = RecordLength.intToByteArray(iclFile.getFileHeader()
				.getRecordLength(), 4);
		data = iclFile.getFileHeader().getBytes();
		texto = iclFile.getFileHeader().toString();
		writeData();

		/* write all data in the cash letters */
		writeCashLetterData();

		/* write the file trailer */
		if (iclFile.getFileControl() != null) {
			len = RecordLength.intToByteArray(iclFile.getFileControl()
					.getRecordLength(), 4);
			data = iclFile.getFileControl().getBytes();
			texto = iclFile.getFileControl().toString();
			writeData();
		}
	}

	/**
	 * Write cash letter data.
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private void writeCashLetterData() throws IOException {
		Iterator<CashLetter> it = iclFile.getCashLetterList().iterator();
		while (it.hasNext()) {
			CashLetter letter = it.next();

			/* write the cash letter header */
			len = RecordLength.intToByteArray(letter.getCashLetterHeader()
					.getRecordLength(), 4);
			data = letter.getCashLetterHeader().getBytes();
			texto = letter.getCashLetterHeader().toString();
			writeData();

			/* write all of the data in the bundles */
			writeBundleData(letter);

			/* write the trailer */
			if (letter.getCashLetterControl() != null) {
				len = RecordLength.intToByteArray(letter.getCashLetterControl()
						.getRecordLength(), 4);
				data = letter.getCashLetterControl().getBytes();
				texto = letter.getCashLetterControl().toString();
				writeData();
			}
		}
	}

	/**
	 * Write bundle data.
	 * 
	 * @param letter
	 *            the letter
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private void writeBundleData(CashLetter letter) throws IOException {
		Iterator<ForwardPresentmentBundle> it = letter
				.getForwardPresentmentBundleList().iterator();
		while (it.hasNext()) {
			ForwardPresentmentBundle bundle = it.next();

			/* write the bundle header */
			len = RecordLength.intToByteArray(bundle.getBundleHeader()
					.getRecordLength(), 4);
			data = bundle.getBundleHeader().getBytes();
			texto = bundle.getBundleHeader().toString();
			writeData();

			/* write the item records */
			writeItemData(bundle);

			/* write the bundle control */
			if (bundle.getBundleControl() != null) {
				len = RecordLength.intToByteArray(bundle.getBundleControl()
						.getRecordLength(), 4);
				data = bundle.getBundleControl().getBytes();
				texto = bundle.getBundleControl().toString();
				writeData();
			}
		}

	}

	/**
	 * Write item data.
	 * 
	 * @param bundle
	 *            the bundle
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private void writeItemData(ForwardPresentmentBundle bundle)
			throws IOException {
		String type = bundle.getBundleHeader().collectionTypeIndicator
				.getFieldData();
		if (type.equals("01")) {
			Iterator<ICLItem> it = bundle.getItemList().iterator();
			while (it.hasNext()) {
				ICLItem item = it.next();

				/* write the check detail record */
				len = RecordLength.intToByteArray(item.getCheckDetail()
						.getRecordLength(), 4);
				data = item.getCheckDetail().getBytes();
				texto = item.getCheckDetail().toString();
				writeData();

				/* write any addendum A records */
				for (int i = 0; i < item.getCheckDetailAddendumAList().size(); i++) {
					len = RecordLength.intToByteArray(item
							.getCheckDetailAddendumAList().get(i)
							.getRecordLength(), 4);
					data = item.getCheckDetailAddendumAList().get(i).getBytes();
					texto = item.getCheckDetailAddendumAList().get(i)
							.toString();
					writeData();
				}

				/* write any addendum C records */
				for (int i = 0; i < item.getCheckDetailAddendumCList().size(); i++) {
					// log.info("EScribiendo addC:"+i);
					len = RecordLength.intToByteArray(item
							.getCheckDetailAddendumCList().get(i)
							.getRecordLength(), 4);
					data = item.getCheckDetailAddendumCList().get(i).getBytes();
					texto = item.getCheckDetailAddendumCList().get(i)
							.toString();
					writeData();
				}

				/* write the image view records */
				writeImageData(item);
			}
		} else {
			Iterator<ReturnItem> it = bundle.getItemList().iterator();
			while (it.hasNext()) {
				ReturnItem item = it.next();

				/* write the check detail record */
				len = RecordLength.intToByteArray(item.getReturnDetail()
						.getRecordLength(), 4);
				data = item.getReturnDetail().getBytes();
				texto = item.getReturnDetail().toString();
				writeData();

				/* write any addendum records */
				// TODO: implement writing of other addendum record types
				for (int i = 0; i < item.getReturnDetailAddendumAList().size(); i++) {
					len = RecordLength.intToByteArray(item
							.getReturnDetailAddendumAList().get(i)
							.getRecordLength(), 4);
					data = item.getReturnDetailAddendumAList().get(i)
							.getBytes();
					texto = item.getReturnDetailAddendumAList().get(i)
							.toString();
					writeData();
				}

				/* write the addendumD 3501 */
				if (item.getReturnAdd3501() != null) {
					len = RecordLength.intToByteArray(item.getReturnAdd3501()
							.getRecordLength(), 4);
					data = item.getReturnAdd3501().getBytes();
					texto = item.getReturnAdd3501().toString();
					writeData();
				}

				/* write the addendumD 3502 */
				if (item.getReturnAdd3502() != null) {
					len = RecordLength.intToByteArray(item.getReturnAdd3502()
							.getRecordLength(), 4);
					data = item.getReturnAdd3502().getBytes();
					texto = item.getReturnAdd3502().toString();
					writeData();
				}

				/* write the addendumD 3503 */
				if (item.getReturnAdd3503() != null) {
					len = RecordLength.intToByteArray(item.getReturnAdd3503()
							.getRecordLength(), 4);
					data = item.getReturnAdd3503().getBytes();
					texto = item.getReturnAdd3503().toString();
					writeData();
				}

				/* write the addendumD 3504 */
				if (item.getReturnAdd3504() != null) {
					len = RecordLength.intToByteArray(item.getReturnAdd3504()
							.getRecordLength(), 4);
					data = item.getReturnAdd3504().getBytes();
					texto = item.getReturnAdd3504().toString();
					writeData();
				}
				/* write the addendumD 3505 */
				if (item.getReturnAdd3505() != null) {
					len = RecordLength.intToByteArray(item.getReturnAdd3505()
							.getRecordLength(), 4);
					data = item.getReturnAdd3505().getBytes();
					texto = item.getReturnAdd3505().toString();
					writeData();
				}
				/* write the addendumD 3506 */
				if (item.getReturnAdd3506() != null) {
					len = RecordLength.intToByteArray(item.getReturnAdd3506()
							.getRecordLength(), 4);
					data = item.getReturnAdd3506().getBytes();
					texto = item.getReturnAdd3506().toString();
					writeData();
				}

				/* write the addendumD 3598 */
				for (int i = 0; i < item.getReturnDetailAddendumD3598List()
						.size(); i++) {
					len = RecordLength.intToByteArray(item
							.getReturnDetailAddendumD3598List().get(i)
							.getRecordLength(), 4);
					data = item.getReturnDetailAddendumD3598List().get(i)
							.getBytes();
					texto = item.getReturnDetailAddendumD3598List().get(i)
							.toString();
					writeData();
				}

				/* write the addendumD 3599 */
				for (int i = 0; i < item.getReturnDetailAddendumD3599List()
						.size(); i++) {
					len = RecordLength.intToByteArray(item
							.getReturnDetailAddendumD3599List().get(i)
							.getRecordLength(), 4);
					data = item.getReturnDetailAddendumD3599List().get(i)
							.getBytes();
					texto = item.getReturnDetailAddendumD3599List().get(i)
							.toString();
					writeData();
				}
				/* write the image view records */
				writeImageData(item);
			}
		}
	}

	/**
	 * Write image data.
	 * 
	 * @param item
	 *            the item
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private void writeImageData(Item item) throws IOException {
		Iterator<ImageView> it = item.getImageViewList().iterator();
		while (it.hasNext()) {
			ImageView view = it.next();

			if (view.getImageViewDetail() != null) {
				/* write the 50 record */
				len = RecordLength.intToByteArray(view.getImageViewDetail()
						.getRecordLength(), 4);
				data = view.getImageViewDetail().getBytes();
				texto = view.getImageViewDetail().toString();
				writeData();
			}

			if (view.getImageViewData() != null) {
				/* write the 52 record */
				len = RecordLength.intToByteArray(view.getImageViewData()
						.getRecordLength(), 4);
				data = view.getImageViewData().getBytes();
				texto = view.getImageViewData().toString();
				writeData();
			}

			/* opt - write the 54 record */
			if (view.getImageViewAnalysis() != null) {
				len = RecordLength.intToByteArray(view.getImageViewAnalysis()
						.getRecordLength(), 4);
				data = view.getImageViewAnalysis().getBytes();
				writeData();
			}
		}
	}

	/**
	 * Write data.
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private void writeData() throws IOException {
		if (oStream == null) {
			log.error("Output stream was null");
			throw new IOException("Output Stream is NULL");
		}
		oStream.write(len);
		/*
		 * change encoding of data if necessary. Currently the way this works is
		 * internally we always have it in ascii. So the only condition we need
		 * to account for is the EBCDIC encoding
		 */
		if (iclFile.getFileEncoding() == FileEncoding.EBCDIC) {
			// log.info("writing:'"+texto+"'");
			data = ISOUtil.asciiToEbcdic(data);
		}
		oStream.write(data);
	}

	/**
	 * Gets the icl file.
	 * 
	 * @return the icl file
	 */
	public ICLFile getIclFile() {
		return iclFile;
	}

	/**
	 * Sets the icl file.
	 * 
	 * @param iclFile
	 *            the new icl file
	 */
	public void setIclFile(ICLFile iclFile) {
		this.iclFile = iclFile;
	}

	/**
	 * Gets the file out.
	 * 
	 * @return the file out
	 */
	public File getFileOut() {
		return fileOut;
	}

	/**
	 * Sets the file out.
	 * 
	 * @param fileOut
	 *            the new file out
	 */
	public void setFileOut(File fileOut) {
		this.fileOut = fileOut;
	}
}
